﻿using System;
using System.Collections.Generic;
using System.Text;

namespace MiniGert
{
  /// <summary>
  /// Ray is a ray that starts at an origin and points into direction.
  /// It can be used to test for intersections with several other primitives.
  /// </summary>
  public class Ray
  {
    /// <summary>
    /// The internal field for the origin of the ray.
    /// </summary>
    protected Point _origin = new Point();

    /// <summary>
    /// The internal field for the direction of the ray.
    /// </summary>
    protected Vector _direction = new Vector();

    /// <summary>
    /// The internal field for the length of the ray.
    /// </summary>
    protected float _length = 1.0f;

    /// <summary>
    /// The internal field for the Zero property.
    /// </summary>
    protected static Ray _zero = new Ray();

    /// <summary>
    /// Creates a new ray at origin (0,0) in direction (0,0) with a lenght of 0.
    /// </summary>
    public Ray()
    {
    }

    /// <summary>
    /// Create a new ray that start at start and pointing to end.
    /// </summary>
    /// <param name="start">The start of the ray.</param>
    /// <param name="end">The point the ray points to.</param>
    public Ray(Point start, Point end)
    {
      _origin = start;
      _direction = Vector.FromPoints(start, end);
      _length = _direction.Magnitude;
      _direction.Normalize();
    }

    /// <summary>
    /// Creates a new ray starting at the given origin and pointing into the given direction.
    /// The length is set to 1.
    /// </summary>
    /// <param name="origin">The origin of the new ray.</param>
    /// <param name="direction">The direction of the new ray.</param>
    public Ray(Point origin, Vector direction)
    {
      _origin = origin;
      _direction = direction;
    }

    /// <summary>
    /// Creates a new ray starting at the given origin, pointing into the given direction and with the given length.
    /// </summary>
    /// <param name="origin">The origin of the new ray.</param>
    /// <param name="direction">The direction of the new ray.</param>
    /// <param name="length">The length of the new ray.</param>
    public Ray(Point origin, Vector direction, float length)
    {
      _origin = origin;
      _direction = direction;
      _length = length;
    }

    /// <summary>
    /// Gets or sets the origin of the ray.
    /// </summary>
    public Point Origin
    {
      get { return _origin; }
      set { _origin = value; }
    }

    /// <summary>
    /// Gets or sets the direction of the ray.
    /// </summary>
    public Vector Direction
    {
      get { return _direction; }
      set { _direction = value; }
    }

    /// <summary>
    /// Gets or sets the length of the ray.
    /// </summary>
    public float Length
    {
      get { return _length; }
      set { _length = value; }
    }

    /// <summary>
    /// Calculates the intersection point of this ray and the given line segment.
    /// If the two ray intersects the line segment the intersection point is returned,
    /// else null is returned.
    /// </summary>
    /// <param name="segment">The line segment to check for intersection with the ray.</param>
    /// <returns>The point of intersection or null if there is no intersection.</returns>
    public Point IntersectLineSegment(LineSegment segment)
    {
      float x1 = _origin.X;
      float y1 = _origin.Y;
      float x2 = _origin.X + _direction.X * _length;
      float y2 = _origin.Y + _direction.Y * _length;

      float x3 = segment.Point1.X;
      float y3 = segment.Point1.Y;
      float x4 = segment.Point2.X;
      float y4 = segment.Point2.Y;

      float denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
      if (denominator == 0.0f)
        return null;

      float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
      float ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator;

      if ((ua >= 0.0f && ua <= 1.0f) && (ub >= 0.0f && ub <= 1.0))
        return new Point(x1 + ua * (x2 - x1), y1 + ua * (y2 - y1));
      else
        return null;
    }

    /// <summary>
    /// Gets a ray at origin (0,0), direction (0,0) and length 0.
    /// </summary>
    public static Ray Zero
    {
      get { return _zero; }
    }

  }

}
